#pragma once

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h> //信号量头文件

using namespace std;

template<class T>
class CircularQueue
{
    static const int defaultnum = 15; //默认容量为15
private:
    inline void P(sem_t* psem) //封装PV操作
    {
        sem_wait(psem);
    }
    inline void V(sem_t* psem)
    {
        sem_post(psem);
    }
    inline void Lock(pthread_mutex_t *pmutex) //用来维护多生产多消费
    {
        pthread_mutex_lock(pmutex); //拿锁
    }
    inline void Unlock(pthread_mutex_t *pmutex)
    {
        pthread_mutex_unlock(pmutex); //解锁
    }
public:
    CircularQueue(int num = defaultnum)
        :_cap(num), _index_c(0), _index_p(0), _cq(_cap)
    {
        sem_init(&_c_datasem, 0, 0); //初始化消费者信号量 0
        sem_init(&_p_spacesem, 0, num); //初始化消费者信号量 num
        
        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }
    ~CircularQueue()
    {
        sem_destroy(&_c_datasem);
        sem_destroy(&_p_spacesem);

        pthread_mutex_destroy(&_c_mutex);
        pthread_mutex_destroy(&_p_mutex);
    }

    void Push(const T& in)
    {
        P(&_p_spacesem); //细节：先申请信号量，再加锁
        //原因：1、P操作本身就是原子的 2、可以一定程度上提高运行效率
        Lock(&_p_mutex); //维护多生产者之间的互斥

        _cq[_index_p++] = in; //添加数据
        _index_p %= _cap; //维护循环

        Unlock(&_p_mutex);
        V(&_c_datasem); //V操作给消费者
    }

    void Pop(T* out)
    {
        P(&_c_datasem);
        Lock(&_c_mutex);

        *out = _cq[_index_c++];
        _index_c %= _cap;
        
        Unlock(&_c_mutex);
        V(&_p_spacesem);
    }

private:
    int _index_c; //维护消费者下标 —>二者都是仅有一份的资源
    int _index_p; //维护生产者下标   访问时需要互斥
    int _cap;  //最大容量
    vector<T> _cq;

    //只能用来维持单生产单消费 —> 只能维护消费者和生产者之间的互斥关系
    //能帮助我们判断当前循环队列是空还是满
    sem_t _c_datasem; //消费者的数据信号量
    sem_t _p_spacesem; //生产者的空间信号量

    pthread_mutex_t _c_mutex; //消费者
    pthread_mutex_t _p_mutex; //生产者
};